﻿using System;
using System.Collections.Generic;
using System.Linq;
using Cjc.Calculation.ExpressionEngine.Excel;
using System.Linq.Expressions;
using Microsoft.Scripting;
using Microsoft.Scripting.Hosting;
using Microsoft.Scripting.Hosting.Providers;
using Microsoft.Scripting.Runtime;

namespace Cjc.Calculation.ExpressionEngine
{
	public class Engine
	{
		private ScriptEngine scriptEngine;
		private IEnumerable<string> variables;
		private Dictionary<SymbolId, Formula> formulas = new Dictionary<SymbolId, Formula>();

		public Engine()
			: this( (IEnumerable<string>)null )
		{
		}

		public Engine( IEnumerable<string> variables )
			: this( Hosting.Excel<ExcelContext>.CreateEngine() )
		{
			this.variables = variables;
		}

		public Engine( ScriptEngine scriptEngine )
		{
			this.scriptEngine = scriptEngine;

			this.NullSourceUnit = new SourceUnit(
				LanguageContext,
				NullTextContentProvider.Null,
				null,
				SourceCodeKind.Expression );
		}

		internal ScriptEngine ScriptEngine { get { return scriptEngine; } }

		internal SourceUnit NullSourceUnit { get; private set; }

		public LanguageContext LanguageContext { get { return HostingHelpers.GetLanguageContext( scriptEngine ); } }

		public Expression CompileExpression( string expressionText )
		{
			return CompileExpression( expressionText, null );
		}

		public Expression CompileExpression( string expressionText, Type resultType )
		{
			var compiler = HostingHelpers.GetLanguageContext( scriptEngine ) as ICompileExpression;

			return ( compiler != null )
				? compiler.CompileExpression( expressionText, resultType )
				: null;
		}

		public Dictionary<SymbolId, Formula> Formulas
		{
			get
			{
				lock ( formulas )
				{
					return formulas.ToDictionary( i => i.Key, i => i.Value );
				}
			}
		}

		public bool ContainsKey( string key )
		{
			return ContainsSymbol( SymbolTable.StringToCaseInsensitiveId( key ) );
		}

		internal bool ContainsSymbol( SymbolId symbol )
		{
			lock ( formulas )
			{
				return formulas.ContainsKey( symbol );
			}
		}

		public Formula this[ string key ]
		{
			get { return this[ SymbolTable.StringToCaseInsensitiveId( key ) ]; }
		}

		internal Formula this[ SymbolId symbol ]
		{
			get { return GetFormula( symbol ); }

			set
			{
				lock ( formulas )
				{
					formulas[ symbol ] = value;
				}
			}
		}

		internal Formula GetFormula( SymbolId symbol )
		{
			lock ( formulas )
			{
				return formulas.ContainsKey( symbol )
					? formulas[ symbol ]
					: formulas[ symbol ] = CreateFormula( symbol );
			}
		}

		public virtual Formula CreateFormula()
		{
			return new Formula( this );
		}

		protected virtual Formula CreateFormula( SymbolId symbol )
		{
			var formula = CreateFormula();
			formula.Key = symbol.ToString();
			formula.Symbol = symbol;

			return formula;
		}

		public virtual Worksheet CreateWorksheet()
		{
			return new Worksheet( this );
		}
	}

	public class Engine<TContext> : Engine where TContext : LanguageContext
	{
		public Engine()
			: base( Hosting.Excel<TContext>.CreateEngine() )
		{
		}
	}
}